/** This is the JTalker server 
 *  @author ChrM */

package info.chrm.jtalkerserver;

import info.chrm.helper.*;
import java.net.*;
import javax.net.ssl.*;
import java.io.*;
import java.util.*;
import java.security.*;

public class JTalkerServer {
    public static final String RELEASE = "0.7.1";
    public static final String CHATROOM_MARKER = "#";
    public static final String GLOBAL_USER_LIST = "users.xml";
    
    public JTalkerServer(int _portNum, String _plugInDir, boolean _use_ssl, String _logfile, String _workdir) {
        this.serverSocket = null;
        this.listening = true;
	this.portNum = _portNum;
	this.useSSL = _use_ssl;
        this.workDir = _workdir;

	this.myLogger = new Logger(_logfile);

	if(!this.loadGlobalUsers()) {
	    this.getLogger().writeError("Can't load global user file ("+ this.workDir + GLOBAL_USER_LIST+"). No user defined.");
            //System.exit(-1);
	} 

	// Show all available PlugIns
	this.listPlugIns(_plugInDir);

	// Start updater
	this.uut = new UsersUpdateTimer(this);
	this.uut.start();
		
	if(this.useSSL)
	   this.getLogger().write("+ Creating secure socket on port " + this.portNum);
	else	    
	   this.getLogger().write("+ Creating socket on port " + this.portNum);

        try {            
	   if(this.useSSL)            
	      this.serverSocket = 
		 SSLServerSocketFactory.getDefault().createServerSocket(this.portNum);
	   else            
	      this.serverSocket = new ServerSocket(this.portNum);
        } catch (IOException _e) {
            this.getLogger().writeError("*** Could not listen on port: " + this.portNum);
            this.getLogger().writeError("*** Error: " + _e.toString());
            System.exit(-1);
        }

	this.getLogger().write("+ OK. Waiting for clients...");

        while (this.listening) {
	   try { 
	       // Create Thread
	       new JTalkerServerThread(this, this.serverSocket.accept()).start();
	   } catch (Exception e) { 
	      this.getLogger().writeError("*** Runtime Error: Can't create thread: " + e.getMessage());
              e.printStackTrace();
	   } 
	}

	try { 
	    this.serverSocket.close();
	}
        catch (Exception e) { 
	      this.getLogger().writeError("*** Runtime Error: Can't close socket: " + e.getMessage());
	}
    }

    /** Gets the logger */
    public Logger getLogger() {
       return this.myLogger;
    }

    /** Print out connected members as XML */
    public synchronized String getXMLMemberList(JTalkerMember _caller) {
       String retVal = "";
       
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  JTalkerMember jtm = (JTalkerMember)this.memberList.get(i);
	  if(jtm != null) {
	     if(!jtm.isChatroom()) {
		retVal += ("<MEMBER name=\"" + jtm.getUserName() + "\"");
		retVal += (" status=\"" + jtm.getUserStatus() + "\"");
		retVal += (" ip=\"" + jtm.getUserIP() + "\"/>");
	     } else {
		if(jtm.isChatroomUser(_caller.getUserName())) {
		   retVal += ("<MEMBER name=\"" + jtm.getUserName() + "\"");
		   retVal += (" status=\"" + jtm.getUserStatus() + "\"");	     
		   retVal += (" type=\"" + JTalkerMember.USER_TYPE_CHATROOM + "\"");
		   retVal += (" ip=\"" + jtm.getUserIP() + "\"/>");		
		}
	     }
	  }
       }

       return retVal;
    }

    /** Gets number of connected members */
    public synchronized int getNumOfMembers() {
       return this.memberList.size();
    }

    /** Removes member by port number from the member list */
    public synchronized boolean removeMember(int _port) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getPort() == _port) {
	     this.memberList.remove(i);
	     return true;
	  }
       }
       // Socket not found
       return false;
    }

    /** Removes member from the member list */
    public synchronized boolean removeMember(JTalkerMember _member) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getUserName().equals(_member.getUserName())) {
	     this.memberList.remove(i);
	     return true;
	  }
       }
       // Member not found
       return false;
    }

    /** Removes a chatroom */
    public boolean removeChatroom(String _room_name, JTalkerMember _caller) {
       // find chatroom
       if(!_room_name.startsWith(CHATROOM_MARKER))
	  _room_name = CHATROOM_MARKER + _room_name;
       
       JTalkerMember jtm = this.getMember(_room_name);
       if(jtm == null)
           return false;

       if(!jtm.isChatroom() || 
	  ((jtm.getChatroomAdmin() != null && !jtm.getChatroomAdmin().equals(_caller.getUserName()) && _caller.getUserRoleValue() < 1)))
	  return false;

       this.getLogger().write("+ Removing Chatroom: " + _room_name + " by '" + _caller.getUserName() + "'");
       if(this.removeMember(jtm)) {
           this.sendUpdateRequestToAll(null);
           return true;
       }

       return false;
    }

    /** Adds a new User to a chatroom */
    public boolean addChatroomUser(String _room_name, String _new_user, JTalkerMember _caller) {
       // find chatroom
       if(!_room_name.startsWith(CHATROOM_MARKER))
	  _room_name = CHATROOM_MARKER + _room_name;
       
       JTalkerMember jtm = this.getMember(_room_name);
       if(jtm == null || 
	  !jtm.isChatroom() || 
	  (!jtm.getChatroomAdmin().equals(_caller.getUserName()) && _caller.getUserRoleValue() < 1))
	  return false;

       if(_new_user != null && JTalkerServer.isAllowedUserName(_new_user)) {
	 this.getLogger().write("+ Adding user '" + _new_user + "' to chatroom " + _room_name + ". Caller: " + _caller.getUserName());

         // Add user and send update request to all chatroom users
	 if(jtm.addChatroomUser(_new_user)) {
             this.sendUpdateRequestTo(jtm.getChatroomUsers());
         } else
             return false;
       }

       return true;
    }

    /** Removes a new User to a chatroom */
    public boolean removeChatroomUser(String _room_name, String _kill_user, JTalkerMember _caller) {
       // find chatroom
       if(!_room_name.startsWith(CHATROOM_MARKER))
	  _room_name = CHATROOM_MARKER + _room_name;
       
       JTalkerMember jtm = this.getMember(_room_name);
       if(jtm == null || 
	  !jtm.isChatroom() || 
	  (!jtm.getChatroomAdmin().equals(_caller.getUserName()) && _caller.getUserRoleValue() < 1))
	  return false;

       // Dont remove the OP
       if(jtm.getChatroomAdmin().equals(_kill_user))
	  return false;

       if(_kill_user != null && JTalkerServer.isAllowedUserName(_kill_user)) {
	 this.getLogger().write("+ Removing user '" + _kill_user + "' from chatroom " + _room_name + ". Caller: " + _caller.getUserName());
	
         // remove user from chatroom and send update reqest to all users
         if(jtm.removeChatroomUser(_kill_user)) {
             this.sendUpdateRequestTo(jtm.getChatroomUsers());
         } else
             return false;
       }

       return true;
    }

    /** Gets all chatroom users as ";;" separated String */
    public String getChatroomUsers(String _room_name, JTalkerMember _caller) {
       // find chatroom
       if(!_room_name.startsWith(CHATROOM_MARKER))
	  _room_name = CHATROOM_MARKER + _room_name;
       
       JTalkerMember jtm = this.getMember(_room_name);
       if(jtm == null || !jtm.isChatroom())
	  return "";

       String [] allCRUsers = jtm.getChatroomUsers();	 
       String retVal = "";

       if(allCRUsers == null)
	  return retVal;
       
       for( int i = 0; i < allCRUsers.length; i++ ) {
	  retVal += allCRUsers[i];
	  if(i < (allCRUsers.length - 1))
	     retVal += ";;";
       }
       
       return retVal;
    }
    
    /** Checks for ivalid session IDs 
     *  @return True, if one or more users with ivalid session IDs are removed */
    public synchronized boolean cleanUpMemberList() {
       boolean retVal = false;
       
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  JTalkerMember tmpMem = ((JTalkerMember)this.memberList.get(i));
	  if(tmpMem != null && !tmpMem.isSessionValid() && !tmpMem.isOnline) {
	     // Remove user from the list of online users
	     this.getLogger().write("+ Removing user (closed session): " + tmpMem.getUserInfo());
	     this.memberList.remove(i);
	     retVal = true;
	  }
       }

       return retVal;
    }

    /** Redirects messages from the chatrooms to all allowed users */
    public synchronized boolean redirectChatroomMessages() {
       // TODO: check this method on daylight ;-)
       
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  JTalkerMember jtm = (JTalkerMember)this.memberList.get(i);
	  if(jtm != null && jtm.isChatroom()) {
	     JTalkerMember.Message tmpMess[] = jtm.getMessages(); // all Messages from the chatroom
	     for( int j = 0; j < jtm.getChatroomUsers().length; j++ ) {
		JTalkerMember tmpMem = this.getMember(jtm.getChatroomUsers()[j]);
		if(tmpMem != null)
		   tmpMem.addMessages(tmpMess);
	     }
	  }
       }

       return true;
    }
    
    /** Checks user login and adds this user to the list or verified the session ID 
     *  @return The new created user or null if this method fails */
    public synchronized JTalkerMember addMember(String _userName, String _userPasswd, Socket _socket, String _type, String _sid) {
       String tmpIPAddr = "" + _socket.getInetAddress().getHostAddress();
       String tmpPort   = "" + _socket.getPort();

       if(_userName != null && _userPasswd != null) {       
	  if(this.checkIfRegistered(_userName, _userPasswd) && !this.isUserOnlineByName(_userName)) {
	     if(this.memberList.add(new JTalkerMember(_userName, tmpIPAddr, tmpPort, "normal", _type))) {
		 JTalkerMember newMember = this.getMember(_userName);
		 // Copy global settings to this object
		 newMember.setUserRole(this.getGlobalUserByName(_userName).getRole());
		 return this.getMember(_userName);
	     }
	  }
       }
       else if(_sid != null) {
	  if(this.isUserOnlineBySID(_sid)) {	    
	     this.getMemberBySID(_sid).updateConnectionData(tmpIPAddr, tmpPort);
	     return this.getMemberBySID(_sid);
	  }
       }
      
       return null;
    }

    /** Adds a member to the list as a "chatroom"
     *  @return The new created member or null if this method fails */
    public synchronized JTalkerMember addChatRoom(String _userName) {
       if(_userName != null && JTalkerServer.isAllowedUserName(_userName)) {
	  _userName = CHATROOM_MARKER + _userName;	  
	  
	  // Check if not already in use
	  if(this.getMember(_userName) != null )
	     return null;
	 
	  this.getLogger().write("+ Creating Chatroom: " + _userName);

	  if(this.memberList.add(new JTalkerMember(_userName, null, null, null, JTalkerMember.USER_TYPE_CHATROOM)))
	     return this.getMember(_userName);
       }      
      
       return null;
    }

    /** Gets Member by socket */
    public synchronized JTalkerMember getMember(Socket _socket) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getPort() == _socket.getPort()) {
	     return (JTalkerMember)this.memberList.get(i);
	  }
       }
       // Socket not found
       return null;
    }

    /** Gets Member by name */
    public synchronized JTalkerMember getMember(String _name) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getUserName().equals(_name)) {
	     return (JTalkerMember)this.memberList.get(i);
	  }
       }
       // Name not found
       return null;
    }

    /** Gets Member by session ID */
    public synchronized JTalkerMember getMemberBySID(String _sid) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getSessionID().equals(_sid)) {
	     return (JTalkerMember)this.memberList.get(i);
	  }
       }
       // Name not found
       return null;
    }

    /** Checks if the user is already online (By user name) */
    private synchronized boolean isUserOnlineByName(String _userName) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getUserName().equals(_userName)) {
	     return true;
	  }
       }

       return false;
    }

    /** Checks if the user is already online (By session ID) */
    private synchronized boolean isUserOnlineBySID(String _sid) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
	  if(((JTalkerMember)this.memberList.get(i)).getSessionID().equals(_sid)) {
	     return true;
	  }
       }
       return false;
    }

    /** Sends an update request to all active users */
    public boolean sendUpdateRequestToAll(String _sender) {
       for( int i = 0; i < this.memberList.size(); i++ ) { 
           try { 
              // Do not send to the causer
              if(_sender != ((JTalkerMember)this.memberList.get(i)).getUserName())
                  ((JTalkerMember)this.memberList.get(i)).sendUpdateRequest();
           } catch (Exception _e) {
              // Something went wrong
	      this.getLogger().writeError("*** Runtime Error: Can't send update request to one member: " + _e.getMessage());
           } 
       }

       return true;
    }
    
    /** Sends an update request to a list of active users */
    public boolean sendUpdateRequestTo(String _users) {
	 String [] tmpUsers = _users.split(",");
         return this.sendUpdateRequestTo(tmpUsers);
    }

    /** Sends an update request to a list of active users */
    public boolean sendUpdateRequestTo(String [] _users) {
	 for( int i = 0; i < _users.length; i++ ) {
           if(this.getMember(_users[i]) != null) {
               this.getMember(_users[i]).sendUpdateRequest();
           }
	 }

       return true;
    }
    
    /** Checks if user is a registered one */
    private boolean checkIfRegistered(String _name, String _passwd) {
       JTalkerParser.GlobalUser tmpUser = null;

       // Creates an MD5 from given passwd and checks it against
       // the global user list.
       
       for( int i = 0; i < this.globalUsers.size(); i++ ) { 
	  tmpUser = (JTalkerParser.GlobalUser)this.globalUsers.get(i);
	  if(tmpUser.getName().equals(_name) && tmpUser.getPasswd().equals(JTalkerServer.makeHash(_passwd)))
	     return true;
       }

       this.getLogger().write("+ Unregistered user tries to connect: " + _name + "/" + _passwd);
       return false;
    }

    /** Checks if user is a registered one (only by name) */
    private boolean checkIfRegistered(String _name) {
       /*JTalkerParser.GlobalUser tmpUser = null;
       
       for( int i = 0; i < this.globalUsers.size(); i++ ) { 
	  tmpUser = (JTalkerParser.GlobalUser)this.globalUsers.get(i);
	  if(tmpUser.getName().equals(_name))
	     return true;
       }*/

       if(this.getGlobalUserByName(_name) != null)
	  return true;

       this.getLogger().write("+ Unregistered user tries to connect: " + _name);
       return false;
    }

    /** Adds global user on the fly */
    public boolean addGlobalUser(String _name, String _passwd) {
       if(_name == null || _passwd == null)	 
	  return false;
       
       if(this.checkIfRegistered(_name))
	  return false;

       if(_name.indexOf(" AT ") != -1 || _name.indexOf("\"") != -1)
	  return false;

       // add this user to global list
       JTalkerParser tmpJtp = new JTalkerParser();       
       this.getLogger().write("+ Try to create a new account...");
       this.globalUsers.add(tmpJtp.createGlobalUserObject(_name, _passwd, null, null));
       // this.writeGlobalUsersFile();
       this.getLogger().write("+ New account successfull created: " + _name + "/" + JTalkerServer.makeHash(_passwd));
       return true;
    }

    /** Gets a global user by name */
    public JTalkerParser.GlobalUser getGlobalUserByName(String _name) {
       JTalkerParser.GlobalUser tmpUser = null;
       
       for( int i = 0; i < this.globalUsers.size(); i++ ) { 
	  tmpUser = (JTalkerParser.GlobalUser)this.globalUsers.get(i);
	  if(tmpUser.getName().equals(_name))
	     return tmpUser;
       }

       return null;
    }

    /** Check for PlugIns */
    private void listPlugIns(String _plugInDir) {
       this.getLogger().write("+ Using following PlugIns:");

       // TODO: Use default plugin dir or null
       if(myPlugInLoader == null)
	 myPlugInLoader = new JTalkerPlugInLoader(this, _plugInDir);
      
       this.getLogger().write("  " + myPlugInLoader.getLoadedPlugIns());       
    }

    /** Gets the PlugInLoader object */
    public JTalkerPlugInLoader getPlugInLoader() {
      return myPlugInLoader;
    }

    /** Gets List of all GlobalUsers.
        @return ";;" - separated String with all global users */
    public String getAllGlobalUsers() {
      String tmpRetVal = "";
      int l = this.globalUsers.size();
      
      for(int i = 0; i < l; i++) {
	 tmpRetVal += ((JTalkerParser.GlobalUser)this.globalUsers.get(i)).getName();
	 if(i < (l - 1))
	    tmpRetVal += ";;";
      }

      return tmpRetVal;
    }

    /** Loads the Global User List */
    private boolean loadGlobalUsers() {
       try { 
	 File myUsers = new File(this.workDir + GLOBAL_USER_LIST);
	 JTalkerParser myParser = new JTalkerParser();
	 
	 if(!myParser.parsing(myUsers))
	    return false;
	 
	 this.globalUsers.clear();

	 for( int i = 0; i < myParser.getGlobalUserNum(); i++ ) { 
	    this.globalUsers.add(myParser.getAGlobalUser(i));
	    //System.out.println("+ " + myParser.getAGlobalUser(i).name);
	 }

	 this.getLogger().write("+ Number of global users: " + this.globalUsers.size());
	 
       } catch (Exception e) { 
	  return false;
       } 

       

       return true;
    }

    /** Static method for generating hashes 
     *  @param _input Input String 
     *  @return Hash value or the input String if this fails */
    public static String makeHash(String _input) {
	try {
	    MessageDigest md5 = MessageDigest.getInstance("MD5");
	    md5.update(_input.getBytes());
	    java.math.BigInteger hash = new java.math.BigInteger(1, md5.digest());
	    return hash.toString(16);
	 } catch (Exception _e) {
	    return _input;
	 }
    }

    /** Static method for checking for allowed user names 
     *  @param _input Input String for checking 
     *  @return True, if the input is an allowed user name */    
    public static boolean isAllowedUserName(String _input) {
        if(_input.length() > 10)
            return false;

	return _input.matches("[A-Za-z0-9-]+");
    }

    // Private members
    private ServerSocket serverSocket;
    private boolean listening;
    private int portNum;
    private boolean useSSL = false;
    private ArrayList memberList = new ArrayList();
    private ArrayList globalUsers = new ArrayList();
    private UsersUpdateTimer uut = null;
    private JTalkerPlugInLoader myPlugInLoader = null;
    private String workDir = "./";

   
    // Test method
    public static String test(String _in) {
       return "Hallo!";
    }
    
    // Static main methode
    public static void main(String[] _args) throws IOException {
       // Greeting message
       System.out.println("\n------    Welcome to JTalker-Server (" + RELEASE + ")    ------");
       System.out.println("------ (c)2003-2009 by ChrM (http://chrm.info) ------\n");
              
       if(!parseParameters(_args)) {
	  System.err.println("\nUSAGE: JTalkerServer [-p PORT_NUMBER] [-x PLUGIN_DIR/] [-s|--ssl] [-l LOGFILE] [-w WORKDIR/] [-h|--help]");
	  System.err.println("If you want to use SSL, Java params 'javax.net.ssl.keyStore' and 'javax.net.ssl.keyStorePassword' must be set!\n\n");
	  return;
       }
       
       JTalkerServer theServer = new JTalkerServer(PARAMS_PORT, PARAMS_PLUGIN_DIR, USE_SSL, PARAMS_LOGFILE, WORKING_DIR);
       System.out.println("\n------      JTalker-Server is going down. Bye...   ------\n");
    }

    /** Parses program params */   
    private static boolean parseParameters(String [] _params) {
       for(int i = 0; i < _params.length; i++) {
	  System.out.println(_params[i]);
	  if(_params[i].equals("-p")) {
	    // parsing port number
	    try {
	       PARAMS_PORT = Integer.parseInt(_params[i+1]); 
	    } catch (Exception _e) {
	       return false;
	    }
	  }
	  else if(_params[i].equals("-x")) {
	    try {
	       PARAMS_PLUGIN_DIR = _params[i+1];
               if(!new File(PARAMS_PLUGIN_DIR).isDirectory()) {
                   System.err.println("*** Plugin directory doesn't exists: " + PARAMS_PLUGIN_DIR);
                   return false;
               }
	    } catch (Exception _e) {
	       return false;
	    }
	  }
	  else if(_params[i].equals("-l")) {
	    try {
	       PARAMS_LOGFILE = _params[i+1];
	    } catch (Exception _e) {
	       return false;
	    }
	  }
          else if(_params[i].equals("-w")) {
	    try {
	       WORKING_DIR = _params[i+1];
               if(!(new File(WORKING_DIR + GLOBAL_USER_LIST).canWrite())) {
                   System.err.println("*** Working dir problem: Can't write in: " + WORKING_DIR + GLOBAL_USER_LIST);
                   return false;
               }
	    } catch (Exception _e) {
	       return false;
	    }
	  }
	  else if(_params[i].equals("-s") || _params[i].equals("--ssl")) {
	     // Check for keystore availability
	     if(checkForSSLStuff())
	       USE_SSL = true;
	     else {
	       USE_SSL = false;
	       System.err.println("*** Can't use SSL.");
	       return false;
	     }
	  }
	  else if(_params[i].equals("-h") || _params[i].equals("--help")) {
	    String tmpOut = "\nUniversal communication server with plugin extensions.";
	    tmpOut += "\n\nParameters:";
	    tmpOut += "\n -p <PORT_NUMBER>  Listen on this port for client connections (default: 4444)";
	    tmpOut += "\n -x <PLUGIN_DIR>   Search in this directory for server plugins (extensions)";
	    tmpOut += "\n -l <LOGFILE>      Write logs to <LOGFILE> instead of stdout.";
	    tmpOut += "\n -w <WORKDIR>      Working Directory. User config will be stored here.";
	    tmpOut += "\n -s, --ssl         Use secure sockets (SSL). Needs a keystore and some java-vars to be set!";
	    tmpOut += "\n -h, --help        Show this help text and exit";
	    System.out.println(tmpOut);
	    return false;
	  }
       }

       return true;
    }

    /** Checks if all SSL stuff is available */
    private static boolean checkForSSLStuff() {
       try { 
	  String keyStore = System.getProperty("javax.net.ssl.keyStore");
	  if(keyStore != null) {
	     boolean keyStoreOK = new File(keyStore).canRead();
	     if(!keyStoreOK)
		System.err.println("*** Can't read " + keyStore);
	     return keyStoreOK;
	  } else {
              System.err.println("*** 'javax.net.ssl.keyStore' not set!");
          }
       } catch (Exception _e) {
	  System.err.println("*** Error: " + _e.toString());
	  return false;
       }

       return false;
    }

    /** Class for asynchron updating the global users file and checking for valid session IDs */
    public class UsersUpdateTimer extends Thread {   
       public JTalkerServer parent = null;
       public boolean terminate = false;
       private int lastLength = 0;	  // Last number of global users
       
       public UsersUpdateTimer (JTalkerServer _parent) {
	  this.parent = _parent;
	  this.lastLength = _parent.globalUsers.size();
       }

       private synchronized boolean writeXMLFile() {
	  try {
	     File outputFile = new File(this.parent.workDir + GLOBAL_USER_LIST);        
	     FileWriter out = new FileWriter(outputFile);
	    
	     // Write Head
	     out.write("<?xml version=\"1.0\"?>\n\n<users>\n");
	     for( int i = 0; i < this.parent.globalUsers.size(); i++ ) {
		JTalkerParser.GlobalUser tmpGU = (JTalkerParser.GlobalUser)this.parent.globalUsers.get(i);
		out.write("  <global_user name=\"" + tmpGU.getName() + 
			  "\" passwd=\"" + tmpGU.getPasswd() + 
			  "\" role=\"" + tmpGU.getRole() + 
			  "\" contacts=\"" + tmpGU.getContacts() + 
			  "\" />\n");
	     }
	     out.write("</users>");

	     out.close();
	     return true;
   
	  } catch (Exception e) {  
	    return false;
	  }
       }
       
       public void run() {
	  boolean firstRun = true;
	  while(!terminate) {
	     if(this.lastLength != this.parent.globalUsers.size()) {
		this.writeXMLFile();	     
		this.lastLength = this.parent.globalUsers.size();
	     }

	     // Clean up user List (kill old sessions)
	     this.parent.cleanUpMemberList();

	     try {
	       this.sleep(5000);   // Update every 5 sec.
	     } catch (Exception e) {} 	     
	  }	  
       }
    }

    // Logger
    private Logger myLogger = null;

    // Set defaults (will be overwritten by users params)
    public static int PARAMS_PORT = 4444;
    public static String PARAMS_PLUGIN_DIR = "./plugins";
    public static String PARAMS_LOGFILE = null;
    public static String WORKING_DIR = "./";
    public static boolean USE_SSL = false;
}
